<?php
// +----------------------------------------------------------------------
// | SwiftPHP [ JUST DO ONE THING WELL ]
// +----------------------------------------------------------------------
// | Copyright (c) 2015-2017  http://swiftphp.zhongwenyu.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: zhongwenyu <zhongwenyu1987@163.com> <http://www.zhongwenyu.com>
// +----------------------------------------------------------------------
// | Times: 2017/2/23 10:27
// +----------------------------------------------------------------------
namespace swift;

class Upload {
    /**
     * 默认上传配置
     * @var array
     */
    private $config = array(
        'mimes'           =>  ['image/bmp','image/gif','image/jpeg','image/pipeg','image/png'], //允许上传的文件MiMe类型
        'maxSize'         =>  0, //上传的文件大小限制 (0-不做限制)
        'exts'            =>  array(), //允许上传的文件后缀
        'autoSub'         =>  false, //自动子目录保存文件
        'subName'         =>  array('date', 'Y-m-d'), //子目录创建方式，[0]-函数名，[1]-参数，多个参数使用数组
        'savePath'        =>  PUBLIC_PATH . 'uploads' . DS, //保存路径
        'rootPath'        =>  '',
        'saveName'        =>  array('uniqid', ''), //上传文件命名规则，[0]-函数名，[1]-参数，多个参数使用数组
        'saveExt'         =>  '', //文件保存后缀，空则使用原后缀
        'replace'         =>  false, //存在同名是否覆盖
        'hash'            =>  true, //是否生成hash编码
        'callback'        =>  false, //检测文件是否存在回调，如果存在返回文件信息数组
        'driver'          =>  '', // 文件上传驱动
        'driverConfig'    =>  array(), // 上传驱动配置
        'autoCut'         =>  false,   // 自动裁剪图片
        'cutSize'         =>  [800,800],   // 裁剪图片比例
        'saveOriginal'    =>  true,    // 是否保存原始图片
        'originalPath'    =>  '',      // 原始图片保存路径
        'originalPrefix'  =>  'original_',  // 原始图片保存前缀
        'autoThumb'       =>  false,   // 自动生成缩略图
        'thumbPath'       =>  '',     // 缩略图保存路径
        'thumbSize'       =>  [150,150],     // 缩略图保存大小
        'thumbPrefix'     =>  'thumb_',   // 缩略图保存前缀
    );

    // 图像处理类
    protected $image = null;
    // 错误信息
    protected $error;
    // 上传文件信息
    protected $files;
    // 本次上传文件
    protected $keyFile = '';
    // 上传成功文件信息
    protected $info;

    /**
     * 构造方法，用于构造上传实例
     * @param array  $config 配置
     * @param string $driver 要使用的上传驱动 LOCAL-本地上传驱动，FTP-FTP上传驱动
     */
    public function __construct($config = [] , $driver = ''){
        !empty($config) && $this->config = array_merge($this->config , $config);
        $this->config = $this->config($this->config);

        // 上传文件数组处理
        $this->files = $this->dealFiles();
    }

    /**
     * 上传文件
     * @param $config 本次上传文件配置
     */
    public function upload($config = []){
        // 设置本次上传配置
        !empty($config) && $this->config = array_merge($this->config , $this->config($config));

        // 选择上传文件
        if(!empty($this->keyFile)){
            $files = $this->files[$this->keyFile];
        }else{
            $files = $this->files;
        }
        if(empty($files)){
            $this->error = "没有上传文件！";
            return false;
        }

        // 检查上传目录
        !empty($this->config['rootPath']) && $this->savePath .= trim(str_replace('/' , DS , $this->rootPath) , DS) . DS;

        if(!is_dir($this->savePath)){
            if(!mkdir($this->savePath , 0777 , true)){
                $this->error = "上传文件目录创建失败，请手动创建！";
                return false;
            }
        }

        // 逐个上传文件
        if(is_array(current($files))){
            foreach($files as $key => $val){
                if(is_array(current($val))){
                    foreach($val as $v){
                        $this->uploadOne($v , true);
                    }
                }else{
                    $this->uploadOne($val);
                }
            }
        }else{
            $this->uploadOne($files);
        }

        if(!empty($this->info)){
            return $this->info;
        }else{
            return false;
        }
    }

    // 单个文件上传
    protected function uploadOne($file , $isArray = false){
        $file['name']  = strip_tags($file['name']);
        // 获取文件真实后缀
        if(function_exists('finfo_open')){
            $finfo   =  finfo_open ( FILEINFO_MIME_TYPE );
        }
        if(isset($finfo)){
            $file['type']   =   finfo_file ( $finfo ,  $file['tmp_name'] );
        }
        $file['ext']    =   pathinfo($file['name'], PATHINFO_EXTENSION);
        // 获取文件hash
        if($this->hash){
            $file['md5']  = md5_file($file['tmp_name']);
            $file['sha1'] = sha1_file($file['tmp_name']);
        }
        // 文件信息检测
        $check = $this->check($file);
        if(!$check){
            if($isArray){
                $this->info[$file['key']][]['error'] = $this->error;
            }else{
                $this->info[$file['key']]['error'] = $this->error;
            }
            return false;
        }

        // 生成保存文件名
        $savename = $this->getSaveName($file);
        if(false == $savename){
            return false;
        } else {
            $file['savename'] = $savename;
        }

        // 检测并创建子目录
        $subpath = $this->getSubPath($file['name']);
        if(false === $subpath){
        } else {
            $file['savepath'] = $subpath;
        }
        // 对图像文件进行严格检测
        $ext = strtolower($file['ext']);
        if(in_array($ext, array('gif','jpg','jpeg','bmp','png','swf'))) {
            $imginfo = getimagesize($file['tmp_name']);
            if(empty($imginfo) || ($ext == 'gif' && empty($imginfo['bits']))){
                $this->error = '非法图像文件！';
                if($isArray){
                    $this->info[$file['key']][]['error'] = '非法图像文件！';
                }else{
                    $this->info[$file['key']]['error'] = '非法图像文件！';
                }
                return false;
            }
        }

        $filename = $file['savepath'] . $file['savename'];
        // 不覆盖同名文件
        if (!$this->replace && is_file($filename)) {
            $this->error = '存在同名文件' . $file['savename'];
            if($isArray){
                $this->info[$file['key']][]['error'] = '存在同名文件' . $file['savename'];
            }else{
                $this->info[$file['key']]['error'] = '存在同名文件' . $file['savename'];
            }
            return false;
        }

        // 是否开启自动裁剪
        $truename = '';
        if($this->autoCut){
            $originalpath = PUBLIC_PATH . 'uploads' . DS . str_replace('/' , DS , trim($this->originalPath , '/'));
            !is_dir($originalpath) && mkdir($originalpath , 0777 , true);
            $truename = $filename;
            $filename = $originalpath . DS . $this->originalPrefix . basename($filename);
        }
        if (!move_uploaded_file($file['tmp_name'], $filename)) {
            $this->error = '文件上传保存错误！';
            if($isArray){
                $this->info[$file['key']][]['error'] = '文件上传保存错误！';
            }else{
                $this->info[$file['key']]['error'] = '文件上传保存错误！';
            }
            return false;
        }else{
            // 自动裁剪
            if($this->autoCut){
                $this->thumbSave($filename , dirname($truename) , $this->cutSize);
                $file['originalname'] = basename($filename);
                $file['originalpath'] = str_replace(DS , '/' , str_replace(PUBLIC_PATH , '/' , $filename));
            }

            if($this->autoThumb){
                // 生成缩略图
                if(!empty($this->config['thumbPath'])){
                    $thumbpath = PUBLIC_PATH . 'uploads' . DS . str_replace('/' , DS , trim($this->thumbPath , '/'));
                }else{
                    $thumbpath = $this->savePath;
                }
                $thumb = $this->thumbSave($filename , $thumbpath , $this->thumbSize , $this->thumbPrefix);
                $file['thumbname'] = $thumb['thumbname'];
                $file['thumbpath'] = $thumb['thumbpath'];
            }

            // 是否删除原始图片
            if($this->saveOriginal === false){
                unlink($filename);
            }

            // 记录保存成功的文件
            unset($file['error'], $file['tmp_name']);
            // 转成url保存路径

            $savepath = str_replace(PUBLIC_PATH , '/' , !empty($truename) ? $truename : $filename);
            $file['urlpath'] = str_replace(DS , '/' , $savepath);
            if($isArray){
                $this->info[$file['key']][] = $file;
            }else{
                $this->info[$file['key']] = $file;
            }
        }
    }


    /**
     * 生成缩略图
     * @param $image   原始图片
     * @param $savepath  保存路径
     * @param $size  缩略图大小
     * @param $prefix  保存文件前缀
     * @param $cutprefix  去除前缀
     * @return mixed
     */
    protected function thumbSave($image , $savepath , $size = [] , $prefix = ''){
        if(is_null($this->image)){
            $this->image = new Image();
        }
        $thumbname = basename($image);
        if($this->autoCut){
            $thumbname = str_replace($this->originalPrefix , '' , $thumbname);
        }
        $thumbname = $prefix . $thumbname;
        $thumbpath = rtrim($savepath , '/,' . DS) . DS . $thumbname;

        // 检测缩略图保存路径
        !is_dir($savepath) && mkdir($savepath , 0777 , true);
        $this->image->open($image);
        $this->image->thumb($size[0],$size[1])->save($thumbpath);
        $return['thumbpath'] = str_replace(DS , '/' , str_replace(PUBLIC_PATH , '/' , $thumbpath));
        $return['thumbname'] = $thumbname;
        return $return;
    }

    /**
     * 获取子目录的名称
     * @param array $file  上传的文件信息
     */
    private function getSubPath($filename) {
        $subpath = '';
        $rule    = $this->subName;
        if (!empty($rule)) {
            if($this->autoSub){
                // 自动创建子目录
                $subpath = $this->getName($rule) . DS;
            }

            // 检测并创建子目录
            $savepath = $this->savePath . $subpath;
            !is_dir($savepath) && mkdir($savepath , 0777 , true);
        }
        return $savepath;
    }

    // 文件信息检测
    protected function check($file){
        // 文件上传失败，捕获错误代码
        if ($file['error']) {
            $this->error($file['error']);
            return false;
        }

        // 检查是否合法上传
        if (!is_uploaded_file($file['tmp_name'])) {
            $this->error = '非法上传文件！';
            return false;
        }

        // 检查文件大小
        if (!$this->checkSize($file['size'])) {
            $this->error = '上传文件大小不符！';
            return false;
        }

        /* 检查文件Mime类型 */
        if (!$this->checkMime($file['type'])) {
            $this->error = '上传文件MIME类型不允许！';
            return false;
        }

        /* 检查文件后缀 */
        if (!$this->checkExt($file['ext'])) {
            $this->error = '上传文件后缀不允许';
            return false;
        }

        return true;
    }

    /**
     * 检查文件大小是否合法
     * @param integer $size 数据
     */
    private function checkSize($size) {
        return !($size > $this->maxSize) || (0 == $this->maxSize);
    }

    /**
     * 检查上传的文件MIME类型是否合法
     * @param string $mime 数据
     */
    private function checkMime($mime) {
        return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes);
    }

    /**
     * 检查上传的文件后缀是否合法
     * @param string $ext 后缀
     */
    private function checkExt($ext) {
        return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts);
    }

    /**
     * 根据上传文件命名规则取得保存文件名
     * @param string $file 文件信息
     */
    private function getSaveName($file) {
        $rule = $this->saveName;
        if (empty($rule)) {
            //保持文件名不变
            $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1);
            $savename = $filename;
        } else {
            $savename = $this->getName($rule, $file['name']);
            if(empty($savename)){
                $this->error = '文件命名规则错误！';
                return false;
            }
        }

        /* 文件保存后缀，支持强制更改文件后缀 */
        $ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt;
        return $savename . '.' . $ext;
    }

    // 配置文件设置
    protected function config(array $config){
        if(!empty($config)){
            /* 调整配置，把字符串配置参数转换为数组 */
            if(!empty($config['mimes'])){
                if(is_string($config['mimes'])) {
                    $config['mimes'] = explode(',', $config['mimes']);
                }
                $config['mimes'] = array_map('strtolower', $config['mimes']);
            }
            if(!empty($config['exts'])){
                if (is_string($config['exts'])){
                    $config['exts'] = explode(',', $config['exts']);
                }
                $config['exts'] = array_map('strtolower', $config['exts']);
            }
        }
        return $config;
    }

    /**
     * 获取错误代码信息
     * @param string $errorNo  错误号
     */
    protected function error($errorNo) {
        switch ($errorNo) {
            case 1:
                $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值！';
                break;
            case 2:
                $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值！';
                break;
            case 3:
                $this->error = '文件只有部分被上传！';
                break;
            case 4:
                $this->error = '没有文件被上传！';
                break;
            case 6:
                $this->error = '找不到临时文件夹！';
                break;
            case 7:
                $this->error = '文件写入失败！';
                break;
            default:
                $this->error = '未知上传错误！';
        }
    }

    /**
     * 转换上传文件数组变量为正确的方式
     * @access private
     * @return array
     */
    public function dealFiles(){
        $files = $_FILES;
        $fileArray = [];
        foreach($files as $key => $val){
            if(!empty($val)){
                $file = [];
                $keys = array_keys($val);
                if(is_array($val['name'])){
                    // 同名多文件上传
                    for($i = 0 ; $i < count($val['name']) ; $i++){
                        foreach($keys as $v){
                            $file[$i]['key'] = $key;
                            $file[$i][$v] = $val[$v][$i];
                        }
                    }
                }else{
                    $file = $val;
                    $file['key'] = $key;
                }
                $fileArray[$key] = $file;
            }
        }
        return $fileArray;
    }

    // 选则上传文件
    public function file($key = null){
        if(!is_null($key)){
            $this->keyFile = $key;
        }
        return $this;
    }

    /**
     * 获取最后一次上传错误信息
     * @return string 错误信息
     */
    public function getError(){
        return $this->error;
    }

    /**
     * 使用 $this->name 获取配置
     * @param  string $name 配置名称
     * @return multitype    配置值
     */
    public function __get($name) {
        return $this->config[$name];
    }

    public function __set($name,$value){
        if(isset($this->config[$name])) {
            $this->config[$name] = $value;
        }
    }

    /**
     * 根据指定的规则获取文件或目录名称
     * @param  array  $rule     规则
     * @param  string $filename 原文件名
     * @return string           文件或目录名称
     */
    private function getName($rule){
        $name = '';
        if(is_array($rule)){ //数组规则
            $func     = $rule[0];
            $param    = (array)$rule[1];
            $name = call_user_func_array($func, $param);
        } elseif (is_string($rule)){ //字符串规则
            if(function_exists($rule)){
                $name = call_user_func($rule);
            } else {
                $name = $rule;
            }
        }
        return $name;
    }
}
